/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Benjamin Muskalla <bmuskalla@eclipsesource.com> - [extract method] Extract method and continue https://bugs.eclipse.org/bugs/show_bug.cgi?id=48056 * Benjamin Muskalla <bmuskalla@eclipsesource.com> - [extract method] Name ambiguous return value in error message - https://bugs.eclipse.org/bugs/show_bug.cgi?id=50607 *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.code; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.ibm.icu.text.MessageFormat; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.IRegion; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.compiler.ITerminalSymbols; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BreakStatement; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.ContinueStatement; import org.eclipse.jdt.core.dom.DoStatement; import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.Initializer; import org.eclipse.jdt.core.dom.LabeledStatement; import org.eclipse.jdt.core.dom.Message; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.TryStatement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.WhileStatement; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.Bindings; import org.eclipse.jdt.internal.corext.dom.LocalVariableIndex; import org.eclipse.jdt.internal.corext.dom.Selection; import org.eclipse.jdt.internal.corext.refactoring.Checks; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext; import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowContext; import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowInfo; import org.eclipse.jdt.internal.corext.refactoring.code.flow.InOutFlowAnalyzer; import org.eclipse.jdt.internal.corext.refactoring.code.flow.InputFlowAnalyzer; import org.eclipse.jdt.internal.corext.refactoring.util.CodeAnalyzer; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.ui.JavaElementLabels; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider; /* package */ class ExtractMethodAnalyzer extends CodeAnalyzer { public static final int ERROR= -2; public static final int UNDEFINED= -1; public static final int NO= 0; public static final int EXPRESSION= 1; public static final int ACCESS_TO_LOCAL= 2; public static final int RETURN_STATEMENT_VOID= 3; public static final int RETURN_STATEMENT_VALUE= 4; public static final int MULTIPLE= 5; /** This is either a method declaration or an initializer */ private BodyDeclaration fEnclosingBodyDeclaration; private IMethodBinding fEnclosingMethodBinding; private int fMaxVariableId; private int fReturnKind; private Type fReturnType; private ITypeBinding fReturnTypeBinding; private FlowInfo fInputFlowInfo; private FlowContext fInputFlowContext; private IVariableBinding[] fArguments; private IVariableBinding[] fMethodLocals; private ITypeBinding[] fTypeVariables; private IVariableBinding fReturnValue; private IVariableBinding[] fCallerLocals; private IVariableBinding fReturnLocal; private ITypeBinding[] fAllExceptions; private ITypeBinding fExpressionBinding; private boolean fForceStatic; private boolean fIsLastStatementSelected; private SimpleName fEnclosingLoopLabel; public ExtractMethodAnalyzer(ICompilationUnit unit, Selection selection) throws CoreException { super(unit, selection, false); } public BodyDeclaration getEnclosingBodyDeclaration() { return fEnclosingBodyDeclaration; } public int getReturnKind() { return fReturnKind; } public boolean extractsExpression() { return fReturnKind == EXPRESSION; } public Type getReturnType() { return fReturnType; } public ITypeBinding getReturnTypeBinding() { return fReturnTypeBinding; } public boolean generateImport() { switch (fReturnKind) { case EXPRESSION: return true; default: return false; } } public IVariableBinding[] getArguments() { return fArguments; } public IVariableBinding[] getMethodLocals() { return fMethodLocals; } public IVariableBinding getReturnValue() { return fReturnValue; } public IVariableBinding[] getCallerLocals() { return fCallerLocals; } public IVariableBinding getReturnLocal() { return fReturnLocal; } public ITypeBinding getExpressionBinding() { return fExpressionBinding; } public boolean getForceStatic() { return fForceStatic; } public ITypeBinding[] getTypeVariables() { return fTypeVariables; } //---- Activation checking --------------------------------------------------------------------------- public RefactoringStatus checkInitialConditions(ImportRewrite rewriter) { RefactoringStatus result= getStatus(); checkExpression(result); if (result.hasFatalError()) return result; fReturnKind= UNDEFINED; fMaxVariableId= LocalVariableIndex.perform(fEnclosingBodyDeclaration); if (analyzeSelection(result).hasFatalError()) return result; int returns= fReturnKind == NO ? 0 : 1; if (fReturnValue != null) { fReturnKind= ACCESS_TO_LOCAL; returns++; } if (isExpressionSelected()) { fReturnKind= EXPRESSION; returns++; } if (returns > 1) { result.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_ambiguous_return_value, JavaStatusContext.create(fCUnit, getSelection())); fReturnKind= MULTIPLE; return result; } initReturnType(rewriter); return result; } private void checkExpression(RefactoringStatus status) { ASTNode[] nodes= getSelectedNodes(); if (nodes != null && nodes.length == 1) { ASTNode node= nodes[0]; if (node instanceof Type) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_type_reference, JavaStatusContext.create(fCUnit, node)); } else if (node.getLocationInParent() == SwitchCase.EXPRESSION_PROPERTY) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_switch_case, JavaStatusContext.create(fCUnit, node)); } else if (node instanceof Annotation || ASTNodes.getParent(node, Annotation.class) != null) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_from_annotation, JavaStatusContext.create(fCUnit, node)); } } } private void initReturnType(ImportRewrite rewriter) { AST ast= fEnclosingBodyDeclaration.getAST(); fReturnType= null; fReturnTypeBinding= null; switch (fReturnKind) { case ACCESS_TO_LOCAL: VariableDeclaration declaration= ASTNodes.findVariableDeclaration(fReturnValue, fEnclosingBodyDeclaration); fReturnType= ASTNodeFactory.newType(ast, declaration, rewriter, new ContextSensitiveImportRewriteContext(declaration, rewriter)); if (declaration.resolveBinding() != null) { fReturnTypeBinding= declaration.resolveBinding().getType(); } break; case EXPRESSION: Expression expression= (Expression)getFirstSelectedNode(); if (expression.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION) { fExpressionBinding= ((ClassInstanceCreation)expression).getType().resolveBinding(); } else { fExpressionBinding= expression.resolveTypeBinding(); } if (fExpressionBinding != null) { if (fExpressionBinding.isNullType()) { getStatus().addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_null_type, JavaStatusContext.create(fCUnit, expression)); } else { ITypeBinding normalizedBinding= Bindings.normalizeForDeclarationUse(fExpressionBinding, ast); if (normalizedBinding != null) { ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fEnclosingBodyDeclaration, rewriter); fReturnType= rewriter.addImport(normalizedBinding, ast, context); fReturnTypeBinding= normalizedBinding; } } } else { fReturnType= ast.newPrimitiveType(PrimitiveType.VOID); fReturnTypeBinding= ast.resolveWellKnownType("void"); //$NON-NLS-1$ getStatus().addError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_determine_return_type, JavaStatusContext.create(fCUnit, expression)); } break; case RETURN_STATEMENT_VALUE: if (fEnclosingBodyDeclaration.getNodeType() == ASTNode.METHOD_DECLARATION) { fReturnType= ((MethodDeclaration) fEnclosingBodyDeclaration).getReturnType2(); fReturnTypeBinding= fReturnType != null ? fReturnType.resolveBinding() : null; } break; default: fReturnType= ast.newPrimitiveType(PrimitiveType.VOID); fReturnTypeBinding= ast.resolveWellKnownType("void"); //$NON-NLS-1$ } if (fReturnType == null) fReturnType= ast.newPrimitiveType(PrimitiveType.VOID); fReturnTypeBinding= ast.resolveWellKnownType("void"); //$NON-NLS-1$ } // !!! -- +/- same as in ExtractTempRefactoring public boolean isLiteralNodeSelected() { ASTNode[] nodes= getSelectedNodes(); if (nodes.length != 1) return false; ASTNode node= nodes[0]; switch (node.getNodeType()) { case ASTNode.BOOLEAN_LITERAL : case ASTNode.CHARACTER_LITERAL : case ASTNode.NULL_LITERAL : case ASTNode.NUMBER_LITERAL : return true; default : return false; } } //---- Input checking ----------------------------------------------------------------------------------- public void checkInput(RefactoringStatus status, String methodName, ASTNode destination) { ITypeBinding[] arguments= getArgumentTypes(); ITypeBinding type= ASTNodes.getEnclosingType(destination); status.merge(Checks.checkMethodInType(type, methodName, arguments)); status.merge(Checks.checkMethodInHierarchy(type.getSuperclass(), methodName, null, arguments)); } private ITypeBinding[] getArgumentTypes() { ITypeBinding[] result= new ITypeBinding[fArguments.length]; for (int i= 0; i < fArguments.length; i++) { result[i]= fArguments[i].getType(); } return result; } private RefactoringStatus analyzeSelection(RefactoringStatus status) { fInputFlowContext= new FlowContext(0, fMaxVariableId + 1); fInputFlowContext.setConsiderAccessMode(true); fInputFlowContext.setComputeMode(FlowContext.ARGUMENTS); InOutFlowAnalyzer flowAnalyzer= new InOutFlowAnalyzer(fInputFlowContext); fInputFlowInfo= flowAnalyzer.perform(getSelectedNodes()); if (fInputFlowInfo.branches()) { String canHandleBranchesProblem= canHandleBranches(); if (canHandleBranchesProblem != null) { status.addFatalError(canHandleBranchesProblem, JavaStatusContext.create(fCUnit, getSelection())); fReturnKind= ERROR; return status; } } if (fInputFlowInfo.isValueReturn()) { fReturnKind= RETURN_STATEMENT_VALUE; } else if (fInputFlowInfo.isVoidReturn() || (fInputFlowInfo.isPartialReturn() && isVoidMethod() && isLastStatementSelected())) { fReturnKind= RETURN_STATEMENT_VOID; } else if (fInputFlowInfo.isNoReturn() || fInputFlowInfo.isThrow() || fInputFlowInfo.isUndefined()) { fReturnKind= NO; } if (fReturnKind == UNDEFINED) { status.addError(RefactoringCoreMessages.FlowAnalyzer_execution_flow, JavaStatusContext.create(fCUnit, getSelection())); fReturnKind= NO; } computeInput(); computeExceptions(); computeOutput(status); if (status.hasFatalError()) return status; adjustArgumentsAndMethodLocals(); compressArrays(); return status; } private String canHandleBranches() { if (fReturnValue != null) return RefactoringCoreMessages.ExtractMethodAnalyzer_branch_mismatch; ASTNode[] selectedNodes= getSelectedNodes(); final ASTNode lastSelectedNode= selectedNodes[selectedNodes.length - 1]; Statement body= getParentLoopBody(lastSelectedNode.getParent()); if (!(body instanceof Block)) return RefactoringCoreMessages.ExtractMethodAnalyzer_branch_mismatch; if (body != lastSelectedNode) { Block block= (Block)body; List<Statement> statements= block.statements(); ASTNode lastStatementInLoop= statements.get(statements.size() - 1); if (lastSelectedNode != lastStatementInLoop) return RefactoringCoreMessages.ExtractMethodAnalyzer_branch_mismatch; } final String continueMatchesLoopProblem[]= { null }; for (int i= 0; i < selectedNodes.length; i++) { final ASTNode astNode= selectedNodes[i]; astNode.accept(new ASTVisitor() { ArrayList<String> fLocalLoopLabels= new ArrayList<String>(); @Override public boolean visit(BreakStatement node) { SimpleName label= node.getLabel(); if (label != null && !fLocalLoopLabels.contains(label.getIdentifier())) { continueMatchesLoopProblem[0]= Messages.format( RefactoringCoreMessages.ExtractMethodAnalyzer_branch_break_mismatch, new Object[] { ("break " + label.getIdentifier()) }); //$NON-NLS-1$ } return false; } @Override public boolean visit(LabeledStatement node) { SimpleName label= node.getLabel(); if (label != null) fLocalLoopLabels.add(label.getIdentifier()); return true; } @Override public void endVisit(ContinueStatement node) { SimpleName label= node.getLabel(); if (label != null && !fLocalLoopLabels.contains(label.getIdentifier())) { if (fEnclosingLoopLabel == null || ! label.getIdentifier().equals(fEnclosingLoopLabel.getIdentifier())) { continueMatchesLoopProblem[0]= Messages.format( RefactoringCoreMessages.ExtractMethodAnalyzer_branch_continue_mismatch, new Object[] { "continue " + label.getIdentifier() }); //$NON-NLS-1$ } } } }); } return continueMatchesLoopProblem[0]; } private Statement getParentLoopBody(ASTNode node) { Statement stmt= null; ASTNode start= node; while (start != null && !(start instanceof ForStatement) && !(start instanceof DoStatement) && !(start instanceof WhileStatement) && !(start instanceof EnhancedForStatement)) { start= start.getParent(); } if (start instanceof ForStatement) { stmt= ((ForStatement)start).getBody(); } else if (start instanceof DoStatement) { stmt= ((DoStatement)start).getBody(); } else if (start instanceof WhileStatement) { stmt= ((WhileStatement)start).getBody(); } else if (start instanceof EnhancedForStatement) { stmt= ((EnhancedForStatement)start).getBody(); } if (start.getParent() instanceof LabeledStatement) { LabeledStatement labeledStatement= (LabeledStatement)start.getParent(); fEnclosingLoopLabel= labeledStatement.getLabel(); } return stmt; } private boolean isVoidMethod() { // if we have an initializer if (fEnclosingMethodBinding == null) return true; ITypeBinding binding= fEnclosingMethodBinding.getReturnType(); if (fEnclosingBodyDeclaration.getAST().resolveWellKnownType("void").equals(binding)) //$NON-NLS-1$ return true; return false; } public boolean isLastStatementSelected() { return fIsLastStatementSelected; } private void computeLastStatementSelected() { ASTNode[] nodes= getSelectedNodes(); if (nodes.length == 0) { fIsLastStatementSelected= false; } else { Block body= null; if (fEnclosingBodyDeclaration instanceof MethodDeclaration) { body= ((MethodDeclaration) fEnclosingBodyDeclaration).getBody(); } else if (fEnclosingBodyDeclaration instanceof Initializer) { body= ((Initializer) fEnclosingBodyDeclaration).getBody(); } if (body != null) { List<Statement> statements= body.statements(); fIsLastStatementSelected= nodes[nodes.length - 1] == statements.get(statements.size() - 1); } } } private void computeInput() { int argumentMode= FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN; fArguments= removeSelectedDeclarations(fInputFlowInfo.get(fInputFlowContext, argumentMode)); fMethodLocals= removeSelectedDeclarations(fInputFlowInfo.get(fInputFlowContext, FlowInfo.WRITE | FlowInfo.WRITE_POTENTIAL)); fTypeVariables= computeTypeVariables(fInputFlowInfo.getTypeVariables()); } private IVariableBinding[] removeSelectedDeclarations(IVariableBinding[] bindings) { List<IVariableBinding> result= new ArrayList<IVariableBinding>(bindings.length); Selection selection= getSelection(); for (int i= 0; i < bindings.length; i++) { ASTNode decl= ((CompilationUnit)fEnclosingBodyDeclaration.getRoot()).findDeclaringNode(bindings[i]); if (!selection.covers(decl)) result.add(bindings[i]); } return result.toArray(new IVariableBinding[result.size()]); } private ITypeBinding[] computeTypeVariables(ITypeBinding[] bindings) { Selection selection= getSelection(); Set<ITypeBinding> result= new HashSet<ITypeBinding>(); // first remove all type variables that come from outside of the method // or are covered by the selection CompilationUnit compilationUnit= (CompilationUnit)fEnclosingBodyDeclaration.getRoot(); for (int i= 0; i < bindings.length; i++) { ASTNode decl= compilationUnit.findDeclaringNode(bindings[i]); if (decl == null || (!selection.covers(decl) && decl.getParent() instanceof MethodDeclaration)) result.add(bindings[i]); } // all all type variables which are needed since a local variable uses it for (int i= 0; i < fArguments.length; i++) { IVariableBinding arg= fArguments[i]; ITypeBinding type= arg.getType(); if (type != null && type.isTypeVariable()) { ASTNode decl= compilationUnit.findDeclaringNode(type); if (decl == null || (!selection.covers(decl) && decl.getParent() instanceof MethodDeclaration)) result.add(type); } } return result.toArray(new ITypeBinding[result.size()]); } private void computeOutput(RefactoringStatus status) { // First find all writes inside the selection. FlowContext flowContext= new FlowContext(0, fMaxVariableId + 1); flowContext.setConsiderAccessMode(true); flowContext.setComputeMode(FlowContext.RETURN_VALUES); FlowInfo returnInfo= new InOutFlowAnalyzer(flowContext).perform(getSelectedNodes()); IVariableBinding[] returnValues= returnInfo.get(flowContext, FlowInfo.WRITE | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN); // Compute a selection that exactly covers the selected nodes IRegion region= getSelectedNodeRange(); Selection selection= Selection.createFromStartLength(region.getOffset(), region.getLength()); List<IVariableBinding> localReads= new ArrayList<IVariableBinding>(); flowContext.setComputeMode(FlowContext.ARGUMENTS); FlowInfo argInfo= new InputFlowAnalyzer(flowContext, selection, true).perform(fEnclosingBodyDeclaration); IVariableBinding[] reads= argInfo.get(flowContext, FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.UNKNOWN); outer: for (int i= 0; i < returnValues.length && localReads.size() < returnValues.length; i++) { IVariableBinding binding= returnValues[i]; for (int x= 0; x < reads.length; x++) { if (reads[x] == binding) { localReads.add(binding); fReturnValue= binding; continue outer; } } } switch (localReads.size()) { case 0: fReturnValue= null; break; case 1: break; default: fReturnValue= null; StringBuffer affectedLocals= new StringBuffer(); for (int i= 0; i < localReads.size(); i++) { IVariableBinding binding= localReads.get(i); String bindingName= BindingLabelProvider.getBindingLabel(binding, BindingLabelProvider.DEFAULT_TEXTFLAGS | JavaElementLabels.F_PRE_TYPE_SIGNATURE); affectedLocals.append(bindingName); if (i != localReads.size() - 1) { affectedLocals.append('\n'); } } String message= MessageFormat.format(RefactoringCoreMessages.ExtractMethodAnalyzer_assignments_to_local, new Object[] { affectedLocals.toString() }); status.addFatalError(message, JavaStatusContext.create(fCUnit, getSelection())); return; } List<IVariableBinding> callerLocals= new ArrayList<IVariableBinding>(5); FlowInfo localInfo= new InputFlowAnalyzer(flowContext, selection, false).perform(fEnclosingBodyDeclaration); IVariableBinding[] writes= localInfo.get(flowContext, FlowInfo.WRITE | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN); for (int i= 0; i < writes.length; i++) { IVariableBinding write= writes[i]; if (getSelection().covers(ASTNodes.findDeclaration(write, fEnclosingBodyDeclaration))) callerLocals.add(write); } fCallerLocals= callerLocals.toArray(new IVariableBinding[callerLocals.size()]); if (fReturnValue != null && getSelection().covers(ASTNodes.findDeclaration(fReturnValue, fEnclosingBodyDeclaration))) fReturnLocal= fReturnValue; } private void adjustArgumentsAndMethodLocals() { for (int i= 0; i < fArguments.length; i++) { IVariableBinding argument= fArguments[i]; // Both arguments and locals consider FlowInfo.WRITE_POTENTIAL. But at the end a variable // can either be a local of an argument. Fix this based on the compute return type which // didn't exist when we computed the locals and arguments (see computeInput()) if (fInputFlowInfo.hasAccessMode(fInputFlowContext, argument, FlowInfo.WRITE_POTENTIAL)) { if (argument != fReturnValue) fArguments[i]= null; // We didn't remove the argument. So we have to remove the local declaration if (fArguments[i] != null) { for (int l= 0; l < fMethodLocals.length; l++) { if (fMethodLocals[l] == argument) fMethodLocals[l]= null; } } } } } private void compressArrays() { fArguments= compressArray(fArguments); fCallerLocals= compressArray(fCallerLocals); fMethodLocals= compressArray(fMethodLocals); } private IVariableBinding[] compressArray(IVariableBinding[] array) { if (array == null) return null; int size= 0; for (int i= 0; i < array.length; i++) { if (array[i] != null) size++; } if (size == array.length) return array; IVariableBinding[] result= new IVariableBinding[size]; for (int i= 0, r= 0; i < array.length; i++) { if (array[i] != null) result[r++]= array[i]; } return result; } //---- Change creation ---------------------------------------------------------------------------------- public void aboutToCreateChange() { } //---- Exceptions ----------------------------------------------------------------------------------------- public ITypeBinding[] getExceptions(boolean includeRuntimeExceptions) { if (includeRuntimeExceptions) return fAllExceptions; List<ITypeBinding> result= new ArrayList<ITypeBinding>(fAllExceptions.length); for (int i= 0; i < fAllExceptions.length; i++) { ITypeBinding exception= fAllExceptions[i]; if (!includeRuntimeExceptions && Bindings.isRuntimeException(exception)) continue; result.add(exception); } return result.toArray(new ITypeBinding[result.size()]); } private void computeExceptions() { fAllExceptions= ExceptionAnalyzer.perform(getSelectedNodes()); } //---- Special visitor methods --------------------------------------------------------------------------- @Override protected void handleNextSelectedNode(ASTNode node) { super.handleNextSelectedNode(node); checkParent(node); } @Override protected boolean handleSelectionEndsIn(ASTNode node) { invalidSelection(RefactoringCoreMessages.StatementAnalyzer_doesNotCover, JavaStatusContext.create(fCUnit, node)); return super.handleSelectionEndsIn(node); } private void checkParent(ASTNode node) { ASTNode firstParent= getFirstSelectedNode().getParent(); do { node= node.getParent(); if (node == firstParent) return; } while (node != null); invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_parent_mismatch); } @Override public void endVisit(CompilationUnit node) { RefactoringStatus status= getStatus(); superCall: { if (status.hasFatalError()) break superCall; if (!hasSelectedNodes()) { ASTNode coveringNode= getLastCoveringNode(); if (coveringNode instanceof Block && coveringNode.getParent() instanceof MethodDeclaration) { MethodDeclaration methodDecl= (MethodDeclaration)coveringNode.getParent(); Message[] messages= ASTNodes.getMessages(methodDecl, ASTNodes.NODE_ONLY); if (messages.length > 0) { status.addFatalError(Messages.format( RefactoringCoreMessages.ExtractMethodAnalyzer_compile_errors, BasicElementLabels.getJavaElementName(methodDecl.getName().getIdentifier())), JavaStatusContext.create(fCUnit, methodDecl)); break superCall; } } status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_only_method_body); break superCall; } fEnclosingBodyDeclaration= (BodyDeclaration)ASTNodes.getParent(getFirstSelectedNode(), BodyDeclaration.class); if (fEnclosingBodyDeclaration == null || (fEnclosingBodyDeclaration.getNodeType() != ASTNode.METHOD_DECLARATION && fEnclosingBodyDeclaration.getNodeType() != ASTNode.FIELD_DECLARATION && fEnclosingBodyDeclaration.getNodeType() != ASTNode.INITIALIZER)) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_only_method_body); break superCall; } else if (ASTNodes.getEnclosingType(fEnclosingBodyDeclaration) == null) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_compile_errors_no_parent_binding); break superCall; } else if (fEnclosingBodyDeclaration.getNodeType() == ASTNode.METHOD_DECLARATION) { fEnclosingMethodBinding= ((MethodDeclaration)fEnclosingBodyDeclaration).resolveBinding(); } if (!isSingleExpressionOrStatementSet()) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_single_expression_or_set); break superCall; } if (isExpressionSelected()) { ASTNode expression= getFirstSelectedNode(); if (expression instanceof Name) { Name name= (Name)expression; if (name.resolveBinding() instanceof ITypeBinding) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_type_reference); break superCall; } if (name.resolveBinding() instanceof IMethodBinding) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_method_name_reference); break superCall; } if (name.resolveBinding() instanceof IVariableBinding) { StructuralPropertyDescriptor locationInParent= name.getLocationInParent(); if (locationInParent == QualifiedName.NAME_PROPERTY || (locationInParent == FieldAccess.NAME_PROPERTY && !(((FieldAccess) name.getParent()).getExpression() instanceof ThisExpression))) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_part_of_qualified_name); break superCall; } } if (name.isSimpleName() && ((SimpleName)name).isDeclaration()) { status.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_name_in_declaration); break superCall; } } fForceStatic= ASTNodes.getParent(expression, ASTNode.SUPER_CONSTRUCTOR_INVOCATION) != null || ASTNodes.getParent(expression, ASTNode.CONSTRUCTOR_INVOCATION) != null; } status.merge(LocalTypeAnalyzer.perform(fEnclosingBodyDeclaration, getSelection())); computeLastStatementSelected(); } super.endVisit(node); } @Override public boolean visit(AnonymousClassDeclaration node) { boolean result= super.visit(node); if (isFirstSelectedNode(node)) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_anonymous_type, JavaStatusContext.create(fCUnit, node)); return false; } return result; } @Override public boolean visit(Assignment node) { boolean result= super.visit(node); if (getSelection().covers(node.getLeftHandSide()) || getSelection().coveredBy(node.getLeftHandSide())) { invalidSelection( RefactoringCoreMessages.ExtractMethodAnalyzer_leftHandSideOfAssignment, JavaStatusContext.create(fCUnit, node)); return false; } return result; } @Override public boolean visit(DoStatement node) { boolean result= super.visit(node); try { int actionStart= getTokenScanner().getTokenEndOffset(ITerminalSymbols.TokenNamedo, node.getStartPosition()); if (getSelection().getOffset() == actionStart) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_after_do_keyword, JavaStatusContext.create(fCUnit, getSelection())); return false; } } catch (CoreException e) { // ignore } return result; } @Override public boolean visit(MethodDeclaration node) { Block body= node.getBody(); if (body == null) return false; Selection selection= getSelection(); int nodeStart= body.getStartPosition(); int nodeExclusiveEnd= nodeStart + body.getLength(); // if selection node inside of the method body ignore method if (!(nodeStart < selection.getOffset() && selection.getExclusiveEnd() < nodeExclusiveEnd)) return false; return super.visit(node); } @Override public boolean visit(ConstructorInvocation node) { return visitConstructorInvocation(node, super.visit(node)); } @Override public boolean visit(SuperConstructorInvocation node) { return visitConstructorInvocation(node, super.visit(node)); } private boolean visitConstructorInvocation(ASTNode node, boolean superResult) { if (getSelection().getVisitSelectionMode(node) == Selection.SELECTED) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_super_or_this, JavaStatusContext.create(fCUnit, node)); return false; } return superResult; } @Override public boolean visit(VariableDeclarationFragment node) { boolean result= super.visit(node); if (isFirstSelectedNode(node)) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_variable_declaration_fragment, JavaStatusContext.create(fCUnit, node)); return false; } return result; } @Override public void endVisit(ForStatement node) { if (getSelection().getEndVisitSelectionMode(node) == Selection.AFTER) { if (node.initializers().contains(getFirstSelectedNode())) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_for_initializer, JavaStatusContext.create(fCUnit, getSelection())); } else if (node.updaters().contains(getLastSelectedNode())) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_for_updater, JavaStatusContext.create(fCUnit, getSelection())); } } super.endVisit(node); } @Override public void endVisit(VariableDeclarationExpression node) { if (getSelection().getEndVisitSelectionMode(node) == Selection.SELECTED && getFirstSelectedNode() == node) { if (node.getLocationInParent() == TryStatement.RESOURCES_PROPERTY) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_resource_in_try_with_resources, JavaStatusContext.create(fCUnit, getSelection())); } } checkTypeInDeclaration(node.getType()); super.endVisit(node); } @Override public void endVisit(VariableDeclarationStatement node) { checkTypeInDeclaration(node.getType()); super.endVisit(node); } private boolean isFirstSelectedNode(ASTNode node) { return getSelection().getVisitSelectionMode(node) == Selection.SELECTED && getFirstSelectedNode() == node; } private void checkTypeInDeclaration(Type node) { if (getSelection().getEndVisitSelectionMode(node) == Selection.SELECTED && getFirstSelectedNode() == node) { invalidSelection(RefactoringCoreMessages.ExtractMethodAnalyzer_cannot_extract_variable_declaration, JavaStatusContext.create(fCUnit, getSelection())); } } private boolean isSingleExpressionOrStatementSet() { ASTNode first= getFirstSelectedNode(); if (first == null) return true; if (first instanceof Expression && getSelectedNodes().length != 1) return false; return true; } }